#include "RunnableThread.h"
#include "ThreadManager.h"
#include <assert.h>
#include "../Utils/PlatTlsUtil.h"

uint32 RunnableThread::sRunnableTlsSlot = GetTlsSlot();

int RunnableThread::TranslateThreadPriority(ThreadPriority threadPriority)
{
	switch (threadPriority)
	{
	case ThreadPriority::TPri_Highest:
		return THREAD_PRIORITY_HIGHEST;
	case ThreadPriority::TPri_Normal:
		return THREAD_PRIORITY_HIGHEST - 1;
	case ThreadPriority::TPri_Lowest:
		return THREAD_PRIORITY_HIGHEST - 2;
	}
	return THREAD_PRIORITY_HIGHEST - 3;
}

//uint32 RunnableThread::GetTlsSlot()
//{
//	uint32 tlsSlot = ::TlsAlloc();
//	assert(IsValidTlsSlot(tlsSlot));
//	return tlsSlot;
//}

RunnableThread::RunnableThread() :
	mThread(nullptr),
	mRunnable(nullptr),
	mThreadPriority(ThreadPriority::TPri_Normal),
	mThreadId(0)
{

}

RunnableThread::RunnableThread(Runnable* runnable, const TCHAR* threadName, ThreadPriority threadPriority, uint32 threadId)
{
	assert(runnable != nullptr);
	mRunnable = runnable;
	mThreadName = threadName;
	mThreadPriority = threadPriority;
	mThread = ::GetCurrentThread();
	mThreadId = threadId;

	ThreadManager::Get().AddThread(mThreadId, this);
}

RunnableThread::~RunnableThread()
{
	ThreadManager::Get().RemoveThread(this);
	if (mThread != nullptr)
		Kill(true);
}

RunnableThread* RunnableThread::Create(Runnable* runnable, const TCHAR* threadName, ThreadPriority threadPriority)
{
	RunnableThread* newThread = new RunnableThread();
	assert(newThread != nullptr);
	if (newThread->CreateInternal(runnable, threadName, threadPriority) == false)
	{
		delete newThread;
		return nullptr;
	}
	return newThread;
}

void RunnableThread::SetThreadPriority(ThreadPriority threadPriority)
{
	mThreadPriority = threadPriority;
	::SetThreadPriority(mThread, TranslateThreadPriority(mThreadPriority));
}

void RunnableThread::Suspend(bool shouldPause /*= true */)
{
	assert(mThread != nullptr);
	if (shouldPause == true)
	{
		SuspendThread(mThread);
	}
	else
	{
		ResumeThread(mThread);
	}
}

bool RunnableThread::Kill(bool shouldWait /*= true */)
{
	assert(mThread != nullptr);
	bool didExitOK = true;
	// Let the runnable have a chance to stop without brute force killing
	if (mRunnable)
	{
		mRunnable->OnKill();
	}
	// If waiting was specified, wait the amount of time. If that fails,
	// brute force kill that thread. Very bad as that might leak.
	if (shouldWait == true)
	{
		// Wait indefinitely for the thread to finish.  IMPORTANT:  It's not safe to just go and
		// kill the thread with TerminateThread() as it could have a mutex lock that's shared
		// with a thread that's continuing to run, which would cause that other thread to
		// dead-lock.  (This can manifest itself in code as simple as the synchronization
		// object that is used by our logging output classes.  Trust us, we've seen it!)
		WaitForSingleObject(mThread, INFINITE);
	}
	// Now clean up the thread handle so we don't leak
	CloseHandle(mThread);
	mThread = NULL;

	return didExitOK;
}

void RunnableThread::WaitForCompletion()
{
	assert(mThread != nullptr);
	// Block until this thread exits
	WaitForSingleObject(mThread, INFINITE);
}

uint32 RunnableThread::Run()
{
	// Assume we'll fail init
	uint32 exitCode = 1;
	assert(mRunnable != nullptr);

	// Initialize the runnable object
	if (mRunnable->Init() == true)
	{
		// Setup TLS for this thread, used by FTlsAutoCleanup objects.
		SetTls();

		// Now run the task that needs to be done
		exitCode = mRunnable->Run();
		// Make sure the stop was call on the thread
		mRunnable->Stop();
		// Allow any allocated resources to be cleaned up
		mRunnable->Exit();

		FreeTls();
	}
	else
	{
		assert(0);
	}

	return exitCode;
}

bool RunnableThread::CreateInternal(Runnable* runnable, const TCHAR* threadName, ThreadPriority threadPriority)
{
	assert(runnable != nullptr);
	mRunnable = runnable;
	mThreadName = threadName;
	mThread = CreateThread(nullptr, 0, _ThreadProc, this, STACK_SIZE_PARAM_IS_A_RESERVATION | CREATE_SUSPENDED, (::DWORD*)&mThreadId);

	if (mThread == nullptr)
	{
		mRunnable = nullptr;
	}
	else
	{
		ThreadManager::Get().AddThread(mThreadId, this);
		ResumeThread(mThread);
		SetThreadPriority(threadPriority);
	}
	return mThread != nullptr;
}

void RunnableThread::SetTls()
{
	assert(::GetCurrentThreadId() == mThreadId);
	assert(IsValidTlsSlot(sRunnableTlsSlot));
	::TlsSetValue(sRunnableTlsSlot, this);
}

void RunnableThread::FreeTls()
{
	assert(::GetCurrentThreadId() == mThreadId);
	assert(IsValidTlsSlot(sRunnableTlsSlot));
	::TlsFree(sRunnableTlsSlot);
}

RunnableThread* RunnableThread::GetRunnableThread()
{
	RunnableThread* runnableThread = (RunnableThread*)::TlsGetValue(sRunnableTlsSlot);
	return runnableThread;
}
